package com.derbysoft.synxisadapter.dswitch2.service.impl;

import com.derbysoft.remote.dswitch.core.*;
import com.derbysoft.synxisadapter.commons.dto.synxis.OTACancelRQ;
import com.derbysoft.synxisadapter.commons.dto.synxis.OTACancelRS;
import com.derbysoft.synxisadapter.commons.exception.BusinessException;
import com.derbysoft.synxisadapter.commons.model.Direction;
import com.derbysoft.synxisadapter.commons.utils.Constant;
import com.derbysoft.synxisadapter.dswitch2.service.TransactionLogService;
import com.derbysoft.synxisadapter.webservice.impl.ResponsePair;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;

import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.List;

@Aspect
public class TransactionAdvice {

    private TransactionLogService transactionLogService;

    private static ThreadLocal<LogInfo> logInfo = new ThreadLocal<LogInfo>();
    private int stackTraceDepth = 5;

    @Around("execution(* com.derbysoft.remote.dswitch.hotel.adapter.hotelprovider.HotelProviderWebService.*(..))")
    public Object logForChannel(ProceedingJoinPoint proceedingJoinPoint) {
        GenericRS rs = null;

        GenericRQ rq = (GenericRQ) proceedingJoinPoint.getArgs()[0];

        getLogInfo().addLogItem(rq, Direction.FROM_CHANNEL);

        try {
            rs = (GenericRS) proceedingJoinPoint.proceed();
        } catch (Throwable throwable) {
            String stackTrace = getStackTraceMessage(throwable);

            rs = createErrorRS(rq, throwable, stackTrace);
            getLogInfo().setException(stackTrace);
        }

        getLogInfo().addLogItem(rs, Direction.TO_CHANNEL);

        transactionLogService.create(getLogInfo());
        logInfo.set(null);
        return rs;
    }

    private String getStackTraceMessage(Throwable throwable) {
        StackTraceElement[] stackTrace = throwable.getStackTrace();
        if (stackTrace.length > stackTraceDepth) {
            StackTraceElement[] newStackTrace = new StackTraceElement[stackTraceDepth];
            System.arraycopy(stackTrace, 0, newStackTrace, 0, stackTraceDepth);

            throwable.setStackTrace(newStackTrace);
        }

        StringWriter stringWriter = new StringWriter();
        throwable.printStackTrace(new PrintWriter(stringWriter));

        throwable.setStackTrace(stackTrace);

        return stringWriter.toString();
    }

    private LogInfo getLogInfo() {
        if (logInfo.get() == null) {
            logInfo.set(new LogInfo());
        }
        return logInfo.get();
    }

    @Around("execution(* com.derbysoft.synxisadapter.dswitch2.service.impl.ProviderWebServiceImpl.invoke(..))")
    public Object logForProvider(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {

        getLogInfo().addLogItem(proceedingJoinPoint.getArgs()[0], Direction.TO_HOTEL);
        Object ret = proceedingJoinPoint.proceed();
        getLogInfo().addLogItem(ret, Direction.FROM_HOTEL);

        return ret;
    }

    @SuppressWarnings("unchecked")
    @Around("execution(* com.derbysoft.synxisadapter.dswitch2.service.impl.ProviderWebServiceImpl.batchCancel(..))")
    public Object logForCancellation(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        List<OTACancelRQ> cancelRQs = (List<OTACancelRQ>) proceedingJoinPoint.getArgs()[0];
        for (OTACancelRQ cancelRQ : cancelRQs) {
            getLogInfo().addLogItem(cancelRQ, Direction.TO_HOTEL);
        }

        Object result = proceedingJoinPoint.proceed();
        List<ResponsePair<OTACancelRQ, OTACancelRS>> responsePairs = (List<ResponsePair<OTACancelRQ, OTACancelRS>>) result;

        for (ResponsePair<OTACancelRQ, OTACancelRS> responsePair : responsePairs) {
            getLogInfo().addLogItem(responsePair.getResponse(), Direction.FROM_HOTEL);
        }
        return result;
    }

    private GenericRS createErrorRS(GenericRQ rq, Throwable throwable, String stackTrace) {
        GenericRS genericRS = new GenericRS();
        genericRS.setErrors(createErrors(throwable, stackTrace));
        genericRS.setHeader(createHeader(rq));
        return genericRS;
    }

    private ResponseHeader createHeader(GenericRQ rq) {
        ResponseHeader responseHeader = new ResponseHeader();
        responseHeader.setTaskId(rq.getHeader().getTaskId());
        return responseHeader;
    }

    private ErrorsDTO createErrors(Throwable e, String stackTrace) {
        ErrorsDTO errorsDTO = new ErrorsDTO();
        ArrayList<ErrorDTO> errorDTOs = new ArrayList<ErrorDTO>();
        if (BusinessException.class.isAssignableFrom(e.getClass())) {
            BusinessException businessException = (BusinessException) e;
            errorDTOs.add(new ErrorDTO(businessException.getErrorCode(),
                    businessException.getMessage(), Constant.SYNXIS_SOURCE));
        } else {
            errorDTOs.add(new ErrorDTO(Constant.SYSTEM_ERROR, stackTrace, Constant.SYNXIS_SOURCE));
        }
        errorsDTO.setErrorList(errorDTOs);
        return errorsDTO;
    }

    public void setTransactionLogService(TransactionLogService transactionLogService) {
        this.transactionLogService = transactionLogService;
    }

    public void setStackTraceDepth(int stackTraceDepth) {
        this.stackTraceDepth = stackTraceDepth;
    }
}
